'use strict';

var log4js = require('log4js');
var commandLineArgs = require('command-line-args')
var named = require('named');
var spawn = require('child_process').spawn;
var dns = require('dns');
var cluster = require('cluster');
var numCPUs = require('os').cpus().length;

var logger = log4js.getLogger('default');
logger.level = 'debug';

if(cluster.isMaster) {
	logger.debug(`master ${process.pid} is running`);
	var workerCc = numCPUs - 2;
	var diedWorkerCc = 0;
	for(var i = 0;i < workerCc;i++) {
		cluster.fork();
	}
	cluster.on('exit', (worker, code, signal) => {
		diedWorkerCc++;
		logger.debug(`worker ${worker.process.pid} died: ${diedWorkerCc} / ${workerCc}`);
		if(diedWorkerCc == workerCc) {
			process.exit(0);
		}
	});
	return;
}

logger.debug(`worker ${process.pid} is running`);

var optionDefinitions = [
  { name: 'btc_port', alias: 'x', type: Number },
  { name: 'btc_default', alias: 'b', type: String },
  { name: 'ltc_port', alias: 'y', type: Number },
  { name: 'ltc_default', alias: 'l', type: String },
];

var options = commandLineArgs(optionDefinitions);

if(!options.btc_default) {
	logger.error('no btc_default specified');
	process.exit(1);
	return;
}
if(!options.btc_port) {
	logger.error('no btc_port specified');
	process.exit(1);
	return;
}

if(!options.ltc_default) {
	logger.error('no ltc_default specified');
	process.exit(1);
	return;
}

if(!options.ltc_port) {
	logger.error('no ltc_port specified');
	process.exit(1);
	return;
}

var ttl = 43200;
var cache = {};

function listenOnThisIPAndPort(ip, port, coin) {
	var key = '[' + coin + ' server@' + ip + ':' + port + ']';

	var server = named.createServer();

	server.listen(port, ip, function() {
		logger.info('%s started', key);
	});

	server.on('error', function(err) {
		logger.error('%s error', key, err);
	});

	server.on('query', function(query) {
		var domain = (query.name()||'').toLowerCase();
		var client = query._client.address || 'unknown';

		logger.info('%s receive query %s from %s', key, domain, client);

		if(justForward(domain)) {
			solveRaw(domain, function(ok, addr) {
				if(!ok) {
					logger.error('%s forward query %s from %s failed', key, domain, client);
					return;
				}

				response(query, domain, addr);
				logger.info('%s forward query %s from %s ok, result is %s', key, domain, client, addr);
			});
			return;
		}

		var dft = options[coin + '_default'];
		if(/^\d+\.\d+\.\d+\.\d+$/.test(dft)) {
			// default is IP
			response(query, domain, dft);
			logger.info('%s response %s from %s ok, result is default ip %s', key, domain, client, dft);
			return;
		}

		// default is domain
		solveRaw(dft, function(ok, addr) {
			if(!ok) {
				logger.error('%s solve default %s failed', key, dft);
				return;
			}

			response(query, domain, addr);
			logger.info('%s response %s from %s ok, result is default ip %s from %s', key, domain, client, addr, dft);
		});
	});

	server._ip = ip;

	function response(query, domain, addr) {
		var target = new named.ARecord(addr);
		query.addAnswer(domain, target, ttl);
		server.send(query);
	}

	return server;
}

function justForward(domain) {
	if(domain == 'vps2.iminer.vip') return true;
	if(domain == 'vps1.iminer.vip') return true;
	if(domain == 'easy2mine.com') return true;
	if(domain == 'ok.f2pool.com') return true;
	if(domain == 'b.top') return true;
	if(domain == 'btc.top' || domain == 'ltc.top') return true;
	if(domain == 'baidu.com' || domain == 'www.baidu.com') return true;
	if(/\.nicehash\.com$/i.test(domain)) {
		if(/btc\.top$/.test(options.btc_default)) {
			return true;
		}
	}
	if(/\.btc\.top$/i.test(domain)) return true;
	if(/\.ltc\.top$/i.test(domain)) return true;
	if(/\.pandabtc\.cn$/i.test(domain)) return true;
	if(/\.easy2mine\.com$/i.test(domain)) return true;
	if(/\.okkong\.com$/i.test(domain)) return true;
	if(/\.b\.top$/i.test(domain)) return true;
	if(/\.ntp\.org$/i.test(domain)) return true;
	return false;
}

function solveRaw(domain, cb) {
	if(/^scrypt\./i.test(domain) && /\.nicehash\.com$/i.test(domain)) {
		cb(true, '59.110.92.192');
		return;
	}

	if(/\.nicehash\.com$/i.test(domain)) {
		cb(true, '39.105.253.159');
		return;
	}

	if(domain == 'baidu.com' || domain == 'www.baidu.com') {
		cb(true, '1.2.3.4');
		return;
	}

	var old = cache[domain];
	if(old && new Date() - old.at < 3600 * 1000) {
		// 5 分钟
		cb(true, old.addr);
		return;
	}
	dns.lookup(domain, function(err, address, family) {
		if(err) {
			logger.error('solveRaw failed: %s', domain, err);
			cb(false);
			return;
		}

		if(family != 4) {
			logger.error('solveRaw failed to get IPv4: %s', domain, family);
			cb(false);
			return;
		}

		cache[domain] = {
			at: new Date(),
			addr: address,
		};
		cb(true, address);
	});
}

function activeCheck()
{
	var domains = Object.keys(cache);
	logger.debug('[ACTIVE] with %d', domains.length);
	domains.forEach(function(domain) {
		dns.lookup(domain, function(err, address, family) {
			if(err) {
				logger.error('[ACTIVE] solveRaw failed: %s', domain, err);
				//cb(false);
				return;
			}

			if(family != 4) {
				logger.error('[ACTIVE] solveRaw failed to get IPv4: %s', domain, family);
				//cb(false);
				return;
			}

			cache[domain] = {
				at: new Date(),
				addr: address,
			};
			logger.info('[ACTIVE] solveRaw for %s good', domain);
			//cb(true, address);
		});
	});
}

setInterval(activeCheck, 600 * 1000);
setTimeout(activeCheck, 60 * 1000);

listenOnThisIPAndPort('0.0.0.0', options.btc_port, 'btc');
listenOnThisIPAndPort('0.0.0.0', options.ltc_port, 'ltc');

